// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using ValidationAspects.Exceptions;
using ValidationAspects.Sdk;

namespace ValidationAspects.Core
{
	internal class TypeValidatorFactory<TClass> : ValidatorFactoryBase<TClass>, ITypeValidatorFactory where TClass : class
	{
		private readonly IEnumerable<MethodInfo> _discoveredMethods;

		public TypeValidatorFactory()
		{
			_discoveredMethods = typeof(TClass).GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Where(m => m.IsDefined(typeof(ValidationMethodAttribute), true));
		}

		#region Overrides of ValidatorFactoryBase<TClass>
		protected override Action<TClass, IValidationContext> ValidateAction
		{
			get
			{
				MethodInfo[] methods = _discoveredMethods.Where(m =>
				                                                {
				                                                	if (m.ReturnType != typeof(void) || m.GetParameters().Count() != 1 || m.GetParameters()[0].ParameterType != typeof(TClass))
				                                                		throw new ValidationException(string.Format("Method [name:{0}] signature must be [void Method({1})] if declaring attribute [type{2}].", m.Name,
				                                                		                                            typeof(TClass), typeof(ValidationMethodAttribute)));

				                                                	return true;
				                                                }).ToArray();
				return (v, c) =>
				       {
				       	List<ValidationException> exceptions = new List<ValidationException>();
				       	object[] methodArgs = new object[] {v};

				       	foreach (MethodInfo method in methods)
				       	{
				       		try
				       		{
				       			method.Invoke(null, methodArgs);
				       		}
				       		catch (TargetInvocationException e)
				       		{
				       			ValidationException validationException = e.InnerException as ValidationException;
				       			if (validationException == null)
				       				throw;

				       			validationException.SetContext(new ValidationContext(v, typeof(TClass), null, method, null, methodArgs));
				       			exceptions.Add(validationException);
				       		}
				       	}

				       	if (exceptions.Count > 0)
				       	{
				       		ValidationException exception = new ValidationContextException(exceptions);
				       		exception.SetContext(new ValidationContext(v, typeof(TClass), null, null, null, null));
				       		throw exception;
				       	}
				       };
			}
		}
		#endregion

		#region Implementation of ITypeValidatorFactory
		public bool HasValidationMethods
		{
			get { return _discoveredMethods.Count() > 0; }
		}
		#endregion
	}
}